package ngat.oss.simulation;

import ngat.util.*;
import ngat.phase2.*;
import ngat.astrometry.*;

import java.util.*;

/** An implementation of ExecutionTimingModel which generates a randomly distributed
 * execution timing estimate based on a base ExecutionTimingModel and some parameters
 * which determine the degree of randomness.
 */
public class BasicStochasticExecutionTimingModel implements ExecutionTimingModel, PropertiesConfigurable {

    /** The model this stochastic model is based on.*/
    ExecutionTimingModel  baseModel;

    public boolean option2 = false;
     
    double minOffsetTime;
    double minReadoutTime;
    double minConfigTime;
    double minSlewTime;

    double maxOffsetTime;
    double maxReadoutTime;
    double maxConfigTime;
    double maxSlewTime;

    public BasicStochasticExecutionTimingModel(ExecutionTimingModel baseModel) {
	this.baseModel = baseModel;
    }

    public void configure(ConfigurationProperties config) throws Exception {

	minOffsetTime  = config.getDoubleValue("min.offset.time");
	minReadoutTime = config.getDoubleValue("min.readout.time");
	minConfigTime  = config.getDoubleValue("min.config.time");
	minSlewTime    = config.getDoubleValue("min.slew.time");


	maxOffsetTime  = config.getDoubleValue("max.offset.time");
	maxReadoutTime = config.getDoubleValue("max.readout.time");
	maxConfigTime  = config.getDoubleValue("max.config.time");
	maxSlewTime    = config.getDoubleValue("max.slew.time");

    }
    
    /** Returns true if the group is feasible (executable) under specified conditions.
     * Delegates to the base model.
     * @param group The group to test.
     * @param time  When we want to test it.
     * @param env   A snapshot of the observing environment at time.
     * @param hist  A snapshot summary of the group's execution history at time.
     */
    public boolean canDo(Group group, long time, EnvironmentSnapshot env, ExecutionStatistics hist) {
	return baseModel.canDo(group, time, env, hist);
    }

    /** Returns true if the group is feasible under specified conditions.
     * Delegates to the base model.
     * @param group The group to test.
     * @param time  When we want to test it.
     * */
	public boolean isFeasible(Group group, long time) {
	return baseModel.isFeasible(group, time);
	}
    /** Returns the execution time of the group. New improved method based on standard timings for each operation.*/
    public long getExecTime(Group group) {
	
	
        int countObs       = 0; // counts obs.
        int countSlews     = 1; // counts distinct source moves - include initial slew onto (first) target
	int countOffsets   = 0; // counts mosaic offsets
	int countExposures = 0; // counts exposures
	int countConfigs   = 0; // counts config changes
	
	double sumExpose  = 0.0;
	
        Observation observation = null;
        Source      source      = null;
        Source  prevSource      = null;
        Position    target      = null;
        Position    prevTarget  = null;
	InstrumentConfig instConfig = null;
        InstrumentConfig prevConfig = null;

        // check for sequencing here and create it if it doesnt exist...
        Vector v = null;
        v = group.getSequence();
        
        if (v == null || v.size() == 0) {
            v = new Vector();
            Iterator it = group.listAllObservations();
            while (it.hasNext()) {
                observation = (Observation)it.next();
                v.add(observation.getName());
            }
        }
	
        int nseq = v.size();
	
        Iterator is = v.iterator();
        while (is.hasNext()) {
            String obsName = (String)is.next();
            observation = group.findObservation(obsName);
	    
            countObs++;
	    
            instConfig = observation.getInstrumentConfig();
	    
            source      = observation.getSource();
            target      = source.getPosition();

            // If this is NOT first obs. There IS a previous source.
            if (countObs != 1) {
                if (source != prevSource) {
                    // New source. Add Slew time
		    countSlews++;
		}
		
		if (! (instConfig.sameAs(prevConfig))) {
		    // New config. Add cfg time
		    countConfigs++;
		}
            }
            prevSource = source;
            prevTarget = target;
	    prevConfig = instConfig;

            // Add up all the contributions to this Obo's time.

            // Mosaics.
            int mosaic = 1;
            switch (observation.getMosaic().getPattern()) {
            case Mosaic.ARRAY:
                mosaic = observation.getMosaic().getCellsRA()*observation.getMosaic().getCellsDec();
                break;
            case Mosaic.SINGLE:
                mosaic = 1;
                break;
            case Mosaic.VERTICAL:
            case Mosaic.HORIZONTAL:
            case Mosaic.SLOPE_UP:
            case Mosaic.SLOPE_DOWN:
                mosaic = 3;
                break;
            case Mosaic.CROSS:
            case Mosaic.X:
                mosaic = 5;
                break;
            case Mosaic.HOLE:
                mosaic = 8;
                break;
            case Mosaic.GRID:
                mosaic = 9;
                break;
            default:
                mosaic = 1;
            }

	    // dont count the first one
	  
            // Multruns.
            int multruns = observation.getNumRuns();

	    countOffsets += (mosaic-1);	   
	    countExposures += multruns*mosaic;
	    
	    sumExpose  += mosaic *multruns*(observation.getExposeTime()); // convert msec to sec
	    
        } // per observation.
	
	double minExecOH = countOffsets*minOffsetTime + 
	    countExposures*minReadoutTime + 
	    countConfigs*minConfigTime + 
	    countSlews*minSlewTime;
		
	double maxExecOH = countOffsets*maxOffsetTime + 
	    countExposures*maxReadoutTime + 	  
	    countConfigs*maxConfigTime + 
	    countSlews*maxSlewTime;

	// this is some random point between MIN and MAX exec time - maybe better way ?
	if (option2) 
	    // OPTION 2
	    return (long)(computeRandom(countOffsets, minOffsetTime, maxOffsetTime)+
			  computeRandom(countExposures, minReadoutTime, maxReadoutTime)+
			  computeRandom(countSlews, minSlewTime, maxSlewTime)+
			  computeRandom(countConfigs, minConfigTime, maxConfigTime) + 
			  sumExpose);
	
	else
	    // OPTION 1
	    return (long)(minExecOH + Math.random()*(maxExecOH - minExecOH) + sumExpose);
	

    }
    
    /** Calculate random timing.*/
    private double computeRandom(int n, double min, double max) {
	double rt = 0.0;
	for (int i = 0; i < n; i++) {
	    rt += Math.random()*(max-min) + min;
	}
	return rt;
    }
	    
    
}
